#include <linux/module.h>
#include <linux/pci.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/msi.h>
#include <linux/mutex.h>
#include <linux/cdev.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include "drv_ao.h"

#define AO_PULSE_HIGH_MASK      0xFFFF0000      /* 脉冲宽度 掩码 */
#define AO_PULSE_CYCLE_MASK     0x0000FFFF      /* 脉冲周期 掩码 */
#define AO_PULSE_HIGH_SHIFT     16              /* 脉冲宽度 偏移 */
#define AO_PULSE_CYCLE_SHIFT    0               /* 脉冲周期 偏移 */
#define AO_PULSE_CYCLE_MIN      20              /* 脉冲周期 最小值 */
#define AO_PULSE_CYCLE_MAX      20000           /* 脉冲周期 最大值 */
#define AO_DEF_PULSE_CYCLE      1000            /* 默认脉冲周期, us */

#define AO_ALARM_MASK           0x00FFFF00      /* 告警 掩码 */
#define AO_ID_MASK              0x000000FF      /* 标识 掩码 */
#define AO_ALARM_SHIFT          8               /* 告警 偏移 */
#define AO_ID_SHIFT             0               /* 标识 偏移 */

/* AO 寄存器*/
enum ao_reg_t {
    AO_VERSION_OFFSET           = 0x04,         /* 版本 寄存器 */
    AO_DATA_OFFSET              = 0x3C,         /* 数据 寄存器 */
    AO_ID_ALARM_OFFSET          = 0x50,         /* 标识和告警 寄存器 */
};

/* 文件操作函数声明 */
static int open_ao(struct inode *inode, struct file *filp);
static int release_ao(struct inode *inode, struct file *filp);
static long ioctl_ao(struct file *filp, unsigned int cmd, 
                     unsigned long arg);

/* 文件操作结构体 */
static const struct file_operations cdev_fops = {
    .owner = THIS_MODULE,
    .open = open_ao,
    .release = release_ao,
    .read = NULL,
    .write = NULL,
    .unlocked_ioctl = ioctl_ao,
    .poll = NULL,
    .llseek = no_llseek,
};

/* 读取 AO 寄存器 */
static u32 read_ao_reg_le(struct pcie_dev_t *pcie_dev, u32 reg)
{
    u32 val = 0;
    unsigned long flags = 0;    /* 用于保存中断状态 */

    /* 获取自旋锁(保存中断状态) */
    spin_lock_irqsave(&pcie_dev->splock, flags);
    val = ioread32(pcie_dev->bar[0] + reg);
    /* 释放自旋锁(恢复中断状态) */
    spin_unlock_irqrestore(&pcie_dev->splock, flags);
    pr_debug("%s  reg=0x%08x, val=0x%08x", __func__, reg, val);

    return val;
}

/* 写入 AO 寄存器 */
static void write_ao_reg_le(struct pcie_dev_t *pcie_dev, u32 reg, u32 val)
{
    unsigned long flags = 0;    /* 用于保存中断状态 */

    /* 获取自旋锁(保存中断状态) */
    spin_lock_irqsave(&pcie_dev->splock, flags);
    iowrite32(val, pcie_dev->bar[0] + reg);
    /* 释放自旋锁(恢复中断状态) */
    spin_unlock_irqrestore(&pcie_dev->splock, flags);
    pr_debug("%s reg=0x%08x, val=0x%08x", __func__, reg, val);
}

/* 配置 AO 脉冲 */
static int set_ao_pulse(struct ao_ctrl_t *pctrl, u8 index, u16 high, u16 cycle)
{
    int ret = 0;
    u32 val = 0;
    u8 i = 0;
    u8 start = 0;
    u8 end = 0;

    /* 获取互斥锁 */
    if (mutex_lock_interruptible(&pctrl->lock)) {
        dev_err(pctrl->dev, "Acquire %s %s mutex failed", __func__, pctrl->name);
        return -ERESTARTSYS;
    }
    if (cycle < AO_PULSE_CYCLE_MIN || cycle > AO_PULSE_CYCLE_MAX) {
        dev_err(pctrl->dev, "Set AO%d pulse failed, %d out of %d~%dus", 
                index, cycle, AO_PULSE_CYCLE_MIN, AO_PULSE_CYCLE_MAX);
        ret = -EFAULT;
        goto unlock;
    }
    if (high > (cycle / 2)) {
        dev_err(pctrl->dev, "Set AO%d pulse failed, %d/%d > 50%%", index, high, cycle);
        ret = -EFAULT;
        goto unlock;
    }
    if (index >= pctrl->num) {
        start = 0;
        end = pctrl->num;
    } else {
        start = index;
        end = index + 1;
    }
    for (i = start; i < end; i++) {
        pctrl->ao[i].pulse.high = high;
        pctrl->ao[i].pulse.cycle = cycle;
        pctrl->ao[i].pulse_width = 10000 * high / cycle;
        val = 0;
        val |= ((u32)high << AO_PULSE_HIGH_SHIFT);
        val |= ((u32)cycle << AO_PULSE_CYCLE_SHIFT);
        pctrl->write_reg(pctrl->pcie_dev, AO_ID_ALARM_OFFSET, i);
        pctrl->write_reg(pctrl->pcie_dev, AO_DATA_OFFSET, val);
        dev_dbg(pctrl->dev, "Set AO%d pulse high %dus cycle %dus success", 
                i, high, cycle);
    }

unlock:
    /* 释放互斥锁 */
    mutex_unlock(&pctrl->lock);

    return 0;
}

/* 打开设备 */
static int open_ao(struct inode *inode, struct file *filp)
{
    /* 获取与 inode 关联的字符设备 */
    struct cdev_dev_t *cdev_dev = container_of(inode->i_cdev, struct cdev_dev_t, cdev);
    struct cdev_ctrl_t *cdev_ctrl = (struct cdev_ctrl_t *)(cdev_dev->parent);
    struct ao_ctrl_t *pctrl = (struct ao_ctrl_t *)(cdev_ctrl->parent);
    struct ao_dev_t *priv = &pctrl->ao[cdev_dev->index];

    /* 检查设备是否有效 */
    if (!priv || !priv->dev) {
        pr_err("Invalid parameters for %s", __func__);
        return -ENODEV;
    }
    /* 记录设备指针 */
    filp->private_data = priv;
    /* 获取互斥锁 */
    if (mutex_lock_interruptible(&priv->lock)) {
        dev_err(priv->dev, "Acquire %s mutex failed", cdev_dev->name);
        return -ERESTARTSYS;
    }
    dev_dbg(priv->dev, "Open %s%d device", cdev_ctrl->name, cdev_dev->index);

    mutex_unlock(&priv->lock);

    return 0;
}

/* 关闭设备 */
static int release_ao(struct inode *inode, struct file *filp)
{
    struct ao_dev_t *priv = filp->private_data;

    if (priv) {
        mutex_lock(&priv->lock);
        dev_dbg(priv->dev, "Release %s device", priv->name);
        mutex_unlock(&priv->lock);
    }

    return 0;
}

/* IOCTL 处理 */
static long ioctl_ao(struct file *filp, unsigned int cmd, 
                     unsigned long arg)
{
    struct ao_dev_t *priv = filp->private_data;
    struct ao_ctrl_t *pctrl = (struct ao_ctrl_t *)(priv->parent);
    long ret = 0;
    struct ao_pulse_t pulse = {0};

    /* 检查参数是否有效 */
    if (!priv || !priv->dev) {
        pr_err("Invalid parameters for %s", __func__);
        return -ENODEV;
    }

    /* 获取互斥锁 */
    if (mutex_lock_interruptible(&priv->lock)) {
        dev_err(priv->dev, "Acquire %s mutex failed", priv->name);
        return -ERESTARTSYS;
    }
    switch (cmd) {
    /* 配置 AO 脉冲 */
    case IOCTL_AO_SET_PULSE:
        if (copy_from_user(&pulse, (void __user *)arg, sizeof(pulse))) {
            dev_err(priv->dev, "Set AO%d pulse copy failed", priv->index);
            ret = -EFAULT;
            goto unlock;
        }
        if (set_ao_pulse(pctrl, priv->index, pulse.high, pulse.cycle) < 0) {
            dev_err(priv->dev, "Set AO%d pulse high %d cycle %d failed", 
                    priv->index, pulse.high, pulse.cycle);
            ret = -EFAULT;
            goto unlock;
        }
        break;
    /* 获取驱动版本号 */
    case IOCTL_AO_GET_DRV_VERSION:
        if (copy_to_user((void __user *)arg, &pctrl->drv_ver, sizeof(pctrl->drv_ver))) {
            dev_err(pctrl->dev, "Get AO driver version %d copy failed", pctrl->drv_ver);
            ret = -EFAULT;
            goto unlock;
        }
        break;
    /* 获取 FPGA 版本号 */
    case IOCTL_AO_GET_FPGA_VERSION:
        if (copy_to_user((void __user *)arg, &pctrl->fpga_ver, sizeof(pctrl->fpga_ver))) {
            dev_err(pctrl->dev, "Get AO FPGA version %d copy failed", pctrl->fpga_ver);
            ret = -EFAULT;
            goto unlock;
        }
        break;
    default:
        dev_err(priv->dev, "Ioctl AO%d cmd=0x%08x, dir=0x%X, size=%u, type=0x%02X('%c'), nr=%u not support\n",
                priv->index, cmd, _IOC_DIR(cmd), _IOC_SIZE(cmd), _IOC_TYPE(cmd), _IOC_TYPE(cmd), _IOC_NR(cmd));
        ret = -ENOTTY;
        break;
    }

unlock:
    mutex_unlock(&priv->lock);

    return ret;
}

/* 初始化 AO 设备 */
static int init_ao_dev(void *parent, struct device *dev, const char *name, 
                       struct ao_dev_t *ao, u8 index)
{
    struct ao_dev_t *pdev = ao;
    struct ao_ctrl_t *pctrl = (struct ao_ctrl_t *)parent;
    int ret = 0;

    pdev->parent = parent;
    pdev->dev = dev;
    /* 初始化互斥锁 */
    mutex_init(&pdev->lock);
    strscpy(pdev->name, name, sizeof(pdev->name));
    pdev->index = index;

    pdev->pulse.high = 0;
    pdev->pulse.cycle = AO_DEF_PULSE_CYCLE;
    set_ao_pulse(pctrl, pdev->index, pdev->pulse.high, pdev->pulse.cycle);

    return ret;
}

/* 初始化 AO 控制器 */
int init_ao_ctrl(struct pcie_dev_t *pcie_dev, struct cdev_dev_t *cdev_dev, 
                 struct ao_ctrl_t *pctrl, struct ao_dev_t *ao, 
                 const char *ctrl_name, u8 dev_num)
{
    int ret = 0;
    int i = 0;
    char name[32];

    pctrl->dev = &pcie_dev->pdev->dev;
    /* 初始化互斥锁 */
    mutex_init(&pctrl->lock);
    snprintf(pctrl->name, sizeof(pctrl->name), "%s", ctrl_name);
    pctrl->num = dev_num;
    pctrl->pcie_dev = pcie_dev;
    pctrl->read_reg = read_ao_reg_le;
    pctrl->write_reg = write_ao_reg_le;
    pctrl->cdev_ctrl.cdev_dev = cdev_dev;
    pctrl->ao = ao;
    pctrl->drv_ver = CDEV_DRV_VERSION(AO_VER_MAJOR, AO_VER_MINOR, AO_VER_PATCH);
    pctrl->fpga_ver = pcie_dev->read_mem(pcie_dev->bar[0] + AO_VERSION_OFFSET);

    /* 初始化字符设备 */
    ret = init_cdev_ctrl(pctrl, &pctrl->cdev_ctrl, pctrl->name, dev_num);
    if (ret < 0) {
        dev_err(pctrl->dev, "Init %s controller failed", pctrl->name);
        goto err_free_dev;
    }
    for (i = 0; i < dev_num; i++) {
        snprintf(name, sizeof(name), "%s%d", pctrl->name, i);
        ret = init_cdev_dev(&pctrl->cdev_ctrl, pctrl->cdev_ctrl.cclass, 
                            &pctrl->cdev_ctrl.cdev_dev[i], &cdev_fops, pctrl->cdev_ctrl.major_devno, name, i);
        if (ret) {
            dev_err(pctrl->dev, "Init %s device failed", name);
            goto err_cleanup_cdev_ctrl;
        }
    }

    /* 初始化 AO 设备*/
    for (i = 0; i < dev_num; i++) {
        init_ao_dev(pctrl, pctrl->dev, pctrl->cdev_ctrl.cdev_dev[i].name, 
                    &pctrl->ao[i], i);
    }

    return 0;

err_cleanup_cdev_ctrl:
    exit_cdev_ctrl(&pctrl->cdev_ctrl);
err_free_dev:

    return ret;
}

/* 注销 AO 控制器 */
void exit_ao_ctrl(struct ao_ctrl_t *pctrl, u8 dev_num)
{
    u8 i = 0;

    if (!pctrl) {
        pr_err("Invalid parameters for %s", __func__);
        return;
    }

    for (i = 0; i < dev_num; i++) {
        exit_cdev_dev(pctrl->cdev_ctrl.cclass, &pctrl->cdev_ctrl.cdev_dev[i]);
    }
    exit_cdev_ctrl(&pctrl->cdev_ctrl);
    dev_info(pctrl->dev, "Release cdev controller success");

    for (i = 0; i < dev_num; i++) {
        mutex_destroy(&pctrl->ao[i].lock);
    }
    /* 注销互斥锁 */
    mutex_destroy(&pctrl->lock);

    dev_info(pctrl->dev, "PCIe character device %s removed", pctrl->name);
}
